using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using WCell.Constants;
using WCell.Constants.World;
using WCell.RealmServer.Database;
using WCell.RealmServer.Entities;
using WCell.RealmServer.Global;
using WCell.RealmServer.Handlers;
using NLog;
using WCell.RealmServer.Chat;
using WCell.RealmServer.Battlegrounds;

namespace WCell.RealmServer.Instances
{
	/// <summary>
	/// Manages all Instance- and Battleground- information of a Character
	/// </summary>
	public class InstanceInfo
	{
		private static readonly Logger log = LogManager.GetCurrentClassLogger();

		/// <summary>
		/// Our character id
		/// </summary>
		private uint m_characterId;

		/// <summary>
		/// Our character
		/// </summary>
		private Character m_character;

		/// <summary>
		/// Keeps track of all timed Instances (Raids and Heroics) that the Character is saved to
		/// </summary>
		private List<BaseInstance> m_Instances;

		/// <summary>
		/// Cooldowns of normal Instances
		/// </summary>
		private List<InstanceCooldown> m_InstanceCooldowns;

		public InstanceInfo(Character player)
			: this(player.EntityId.Low)
		{
			m_character = player;
		}

		public InstanceInfo(uint lowId)
		{
			m_Instances = new List<BaseInstance>();
			m_InstanceCooldowns = new List<InstanceCooldown>();
			m_characterId = lowId;
		}

		internal void ReconnectCharacter(Character chr)
		{
			Character = chr;
		}

		#region Properties

		public bool HasFreeInstanceSlot
		{
			get
			{
                if (m_InstanceCooldowns.Count < InstanceMgr.MaxInstancesPerCharPerHour)
                    return true;
                
                CheckCooldowns();
			    return (m_InstanceCooldowns.Count < InstanceMgr.MaxInstancesPerCharPerHour);
			}
		}
                 
        public List<BaseInstance> Instances
	    {
            get { return m_Instances; }
	    }

		public Character Character
		{
			get { return m_character; }
			internal set
			{
				m_character = value;
				if (m_character != null)
				{
					m_characterId = m_character.EntityId.Low;
				}
			}
		}

		/// <summary>
		/// EntityId.Low of the Owner of this log
		/// </summary>
		public uint CharacterId
		{
			get { return m_characterId; }
		}

		#endregion

		#region Instances
        /// <summary>
        /// Updates the List of stored InstanceCooldowns, removing the expired ones.
        /// </summary>
        private void CheckCooldowns()
		{
		    var newList = new List<InstanceCooldown>(m_InstanceCooldowns.Count);
            foreach (var cooldown in m_InstanceCooldowns)
            {
                if (cooldown.EnterTime.AddMinutes(InstanceMgr.NormalExpiryMinutes) > DateTime.Now)
                    continue;
                newList.Add(cooldown);
            }
		    m_InstanceCooldowns = newList;

            //for (var i = 0; i < m_InstanceCooldowns.Count; i++)
            //{
            //    var time = m_InstanceCooldowns[i];
            //    if (time.EnterTime.AddMinutes(InstanceMgr.NormalExpiryMinutes) <= DateTime.Now)
            //    {
            //        m_InstanceCooldowns.RemoveAt(i);
            //        return true;
            //    }
            //}
            //return false;
		}
        
        public void ClearCooldowns()
		{
			m_InstanceCooldowns.Clear();
		}
        
        /// <summary>
		/// Registers an Instance Run against the Player's MaxInstancesPerCharPerHour limit.
		/// </summary>
		/// <param name="instance">The Instance the Character is running.</param>
		/// <param name="time">The DateTime to start counting down from.</param>
		public void SetCooldown(BaseInstance instance, DateTime time)
		{
		    CheckCooldowns();
			if (m_InstanceCooldowns.Count >= InstanceMgr.MaxInstancesPerCharPerHour)
			{
				log.Error("{0} was saved to \"{1}\" but exceeded the MaxInstancesPerCharPerHour limit.",
					m_character, instance);
			}
			m_InstanceCooldowns.Add(new InstanceCooldown(instance.InstanceId, instance.Id, time));
		}

        /// <summary>
        /// Returns the Cooldown object for the Instance with the given MapId.
        /// </summary>
        /// <param name="map">The MapId of the Instance in question.</param>
        /// <returns></returns>
		public InstanceCooldown GetCooldown(MapId map)
		{
			foreach (var cd in m_InstanceCooldowns)
			{
				if (cd.RegionId == map)
				{
					return cd;
				}
			}
			return null;
		}

        /// <summary>
        /// Checks the list of stored Raid and Heroic instances for match against the given MapId.
        /// </summary>
        /// <param name="mapid">The MapId of the Instance in question.</param>
        /// <returns>The Instance or null if not found.</returns>
		public BaseInstance GetTimedInstance(MapId mapid)
		{
			for (var i = 0; i < m_Instances.Count; i++)
			{
				var inst = m_Instances[i];
				if (inst.Id == mapid)
				{
					return inst;
				}
			}
			return null;
		}

        /// <summary>
        /// Checks the list of stored Raid and Heroic instances and the list of recently run Normal 
        /// instances for a reference to the given region.
        /// </summary>
        /// <param name="info">The RegionInfo of the Instance in question.</param>
        /// <returns>The Instance if found, else null.</returns>
		public BaseInstance GetActiveInstance(RegionInfo info)
		{
			var instance = GetTimedInstance(info.Id);
			if (instance == null)
			{
				var id = GetCooldown(info.Id);
				if (id != null)
				{
					instance = World.GetInstance(id);
				}
			}
			return instance;
		}

		/// <summary>
		/// Tries to reset all owned Instances
		/// </summary>
		public void ResetInstances()
		{
			var chr = Character;
			for (var i = 0; i < m_InstanceCooldowns.Count; i++)
			{
				var cooldown = m_InstanceCooldowns[i];
				var instance = World.GetInstance(cooldown);
				if (instance != null)
				{
					if (chr == null || instance.CanReset(chr))
					{
						instance.Reset();
					}
					else
					{
						InstanceHandler.SendResetFailure(Character, instance.Id, InstanceResetFailed.PlayersInside);
						break;
					}
				}
			}
		}

        /// <summary>
        /// Sends the list of Raids completed and In progress and when they will reset.
        /// </summary>
		public void SendRaidTimes()
		{
			if (Character != null)
			{
				SendRaidTimes(Character);
			}
		}

        /// <summary>
        /// Sends the list of Raids completed and In progress and when they will reset.
        /// </summary>
		public void SendRaidTimes(IChatTarget listener)
        {
            if (m_character == null) return;
            
            for (var i = 0; i < m_Instances.Count; i++)
            {
                var instance = m_Instances[i];
                if (instance != null && instance.Expires)
                {
                    listener.SendMessage("Raid {0} #{1}, Until: {1}", instance.RegionId, instance.InstanceId, instance.ExpiryTime);
                }
            }
        }

        public void SaveTimedInstance(BaseInstance instance)
        {
            if (GetTimedInstance(instance.Id) != null) return;
            m_Instances.Add(instance);
        }

	    public IEnumerator<BaseInstance> GetEnumerator()
		{
			foreach (var ir in m_Instances)
			{
				if (ir != null)
				{
					yield return ir;
				}
			}
		}

		#endregion
	}
}
